package DAO;

import Entities.BaseEntity;
import Exceptions.InvalidEntityException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.sql.Statement;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;

/**
 *
 * @author 647_2
 *
 * Класс предоставляет доступ к данным
 */
public class Accessor {

    /***
     * Ссылка на класс (для получения его названия)
     */
    private Class entity;
    /***
     * Сервер базы данных
     */
    private String host;
    /***
     * Имя пользователя базы данных
     */
    private String login;
    /***
     * Пароль пользователя базы данных
     */
    private String pass;
    /***
     * Таблица для работы
     */
    private String table;
    /***
     * Соединения полей записей в базе данных и полей в классах-сущностях, массив для полей сущностей
     */
    private LinkedList<String> links_in;
    /***
     * Соединения полей записей в базе данных и полей в классах-сущностях, массив для полей записей в базе данных
     */
    private LinkedList<String> links_out;
    /***
     * Соединения полей записей в базе данных и полей в классах-сущностях, массив для типов полей
     */
    private LinkedList<String> links_types;

    /***
     * Конструктор, принимающий сам класс
     * @param entity Класс для последующей работы с этими типами сущностей
     */
    public Accessor(Class entity) {
        this.entity = entity;
    }

    /***
     * Конструктор, принимающий саму сущность
     * @param entity Сущность для последующей работы с этими типами сущностей
     */
    public Accessor(BaseEntity entity) {
        this.entity = entity.getClass();
    }

    /***
     * Загрузка параметров соединения сущности с базой
     * @param config Путь к файлу с параметрами
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void ConfigLoader(String config) throws FileNotFoundException, IOException {
        BufferedReader br = new BufferedReader(new FileReader(config));

        while (br.ready()) {
            String line = br.readLine();
            String left = line.substring(0, line.indexOf("="));
            String right = line.substring(line.indexOf("=") + 1);
            if (left.equalsIgnoreCase("tablename")) {
                this.table = right;
            } else {
                if ((left.trim().length() > 0) && (right.trim().length() > 0)) {
                    links_in.addLast(left.substring(0, left.indexOf("|")));
                    links_types.addLast(left.substring(left.indexOf("|") + 1));
                    links_out.addLast(right);
                }
            }
        }
    }

    /***
     * Загрузка глобальных параметров доступа к базе данных
     * @param config Путь к файлу с параметрами
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void GlobalConfigLoader(String config) throws FileNotFoundException, IOException {
        BufferedReader br = new BufferedReader(new FileReader(config));
        while (br.ready()) {
            String line = br.readLine();
            String left = line.substring(0, line.indexOf("="));
            String right = line.substring(line.indexOf("=") + 1);
            if (left.equalsIgnoreCase("host")) {
                this.host = right;
            }
            if (left.equalsIgnoreCase("login")) {
                this.login = right;
            }
            if (left.equalsIgnoreCase("password")) {
                this.pass = right;
            }
        }
    }

    /***
     * Сокращённый метод загрузки параметров, использует метод получения стандартного пути по имени сущности
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void ConfigLoader() throws FileNotFoundException, IOException {
        this.ConfigLoader(this.getConfigNameByClass());
    }

    /***
     * Сокращённый метод загрузки глобальных параметров, использует метод получения стандартного пути
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void GlobalConfigLoader() throws FileNotFoundException, IOException {
        this.GlobalConfigLoader(this.getGlobalConfigName());
    }

    /***
     * Получение имени таблицы
     * @param table Имя таблицы
     */
    public String getTable() {
        return this.table;
    }

    /***
     * Установка имени таблицы
     * @param table Имя таблицы
     */
    public void setTable(String table) {
        this.table = table;
    }

    /***
     * Получение набора ссылок (поля сущностей)
     * @return Набор ссылок (поля сущностей)
     */
    public LinkedList<String> getLinks_in() {
        return links_in;
    }

    /***
     * Установка набора ссылок (поля сущностей)
     * @param links Набор ссылок (поля сущностей)
     */
    public void setLinks_in(LinkedList<String> links) {
        this.links_in = links;
    }

    /***
     * Получение набора ссылок (поля записей в БД)
     * @return Набор ссылок (поля записей в БД)
     */
    public LinkedList<String> getLinks_out() {
        return links_out;
    }

    /***
     * Установка набора ссылок (поля записей в БД)
     * @param links Набор ссылок (поля записей в БД)
     */
    public void setLinks_out(LinkedList<String> links) {
        this.links_out = links;
    }

    /***
     * Получение набора ссылок (типы)
     * @return Набор ссылок (типы)
     */
    public LinkedList<String> getLinks_types() {
        return links_types;
    }

    /***
     * Установка набора ссылок (типы)
     * @param links Набор ссылок (типы)
     */
    public void setLinks_types(LinkedList<String> links_types) {
        this.links_types = links_types;
    }

    /***
     * Метод получения стандартного пути к файлу параметров с использованием имени сущности
     * @return Путь к файлу параметров
     */
    public String getConfigNameByClass() {
        return System.getProperties().get("user.dir") + "/сonfigs/" + this.entity.getSimpleName() + ".dbc";
    }

    /***
     * Метод получения стандартного пути к файлу глобальных параметров
     * @return Путь к файлу глобальных параметров
     */
    public String getGlobalConfigName() {
        return System.getProperties().get("user.dir") + "/сonfigs/database.dbc";
    }

    /***
     * Получение адреса сервера
     * @return Адрес сервера
     */
    public String getHost() {
        return host;
    }

    /***
     * Установка адреса сервера
     * @param host Адрес сервера
     */
    public void setHost(String host) {
        this.host = host;
    }

    /***
     * Получение имени пользователя базы данных
     * @return Имя пользователя базы данных
     */
    public String getLogin() {
        return login;
    }

    /***
     * Установка имени пользователя базы данных
     * @param login Имя пользователя базы данных
     */
    public void setLogin(String login) {
        this.login = login;
    }

    /***
     * Получение пароля пользователя базы данных
     * @return Пароль пользователя базы данных
     */
    public String getPass() {
        return pass;
    }

    /***
     * Установка пароля пользователя базы данных
     * @param pass Пароль пользователя базы данных
     */
    public void setPass(String pass) {
        this.pass = pass;
    }

    /***
     * Инициализация класса по файлам параметров по стандартным путям
     */
    public void Initialize() throws FileNotFoundException, IOException {
        links_in = new LinkedList<String>();
        links_types = new LinkedList<String>();
        links_out = new LinkedList<String>();
        this.ConfigLoader();
        this.GlobalConfigLoader();
    }

    public <T extends BaseEntity> LinkedList<T> select(T selector, Long start, Long count) throws InvalidEntityException, ClassNotFoundException, SQLException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, UnsupportedEncodingException, NoSuchFieldException, FileNotFoundException, IOException {

        System.out.println("Запуск селектора 1");

        System.out.println(selector.getClass());
        System.out.println(this.entity);

        if (selector.getClass().equals(this.entity) == false) {
            throw new InvalidEntityException();
        }

        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://" + this.host + "/mydb?useUnicode=true&characterEncoding=UTF-8", this.login, this.pass);

        Statement statement = connection.createStatement();

        String SQLQuery = "SELECT ";
        String link;
        String where = "";
        Object param;

        for (int i = 0; i < this.links_out.size(); i++) {
            link = this.links_out.get(i);

            //TODO Добавить потом возможность JOIN

            if (link.indexOf("|") > -1) {
                String link_left = link.substring(0, link.indexOf("|"));
                String link_right = link.substring(link.indexOf("|") + 1);
                if (selector.getClass().getMethod("get_" + this.links_in.get(i)).invoke(selector) != null) {
                    param = selector.getClass().getMethod("get_" + this.links_in.get(i)).invoke(selector).getClass().getMethod("get_" + link_right).invoke(selector.getClass().getMethod("get_" + this.links_in.get(i)).invoke(selector));
                } else {

                    param = null;
                }

                link_left = escape(link_left);
                SQLQuery += "`" + table + "`.`" + link_left + "`, ";

                if (param != null) {
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    where += "`" + link_left + "` = '" + param + "' AND ";
                }

            } else {

                link = escape(link);
                SQLQuery += "`" + table + "`.`" + link + "`, ";

                param = selector.getClass().getMethod("get_" + this.links_in.get(i)).invoke(selector);

                if (param != null) {
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    where += "`" + link + "` = '" + param + "' AND ";
                }

            }

        }

        SQLQuery = SQLQuery.substring(0, SQLQuery.length() - 2);

        SQLQuery += " FROM `" + this.table + "`";

        if (where.equals("") == false) {
            SQLQuery += " WHERE " + where.substring(0, where.length() - 4);
        }

        if (count > 0) {
            SQLQuery += " LIMIT " + start + ", " + count;
        }

        //SQLQuery = new String(SQLQuery.getBytes("UTF-8"),"windows-1251");

        System.out.println("Производится запрос: " + SQLQuery);

        ResultSet result = statement.executeQuery(SQLQuery);

        LinkedList<T> responses = new LinkedList<T>();

        while (result.next()) {

            T response = (T) entity.newInstance();

            for (int i = 0; i < links_in.size(); i++) {

                if (this.links_out.get(i).indexOf("|") > -1) {
                    link = this.links_out.get(i);
                    String link_left = link.substring(0, link.indexOf("|"));
                    String link_right = link.substring(link.indexOf("|") + 1);

                    Object c = Class.forName("Entities." + this.links_types.get(i)).newInstance();
                    c.getClass().getMethod("set_" + link_right, Long.class).invoke(c, result.getLong(i + 1));
                    Object c2 = SimpleAccessorFactory.createSimpleAccessor(c.getClass()).select((T) c).getFirst();
                    response.getClass().getMethod("set_" + this.links_in.get(i), Class.forName("Entities." + this.links_types.get(i))).invoke(response, c2);

                } else {
                    if (this.links_types.get(i).equals("Long") == true) {
                        response.getClass().getMethod("set_" + this.links_in.get(i), Long.class).invoke(response, result.getLong(i + 1));
                    } else if (this.links_types.get(i).equals("String") == true) {
                        response.getClass().getMethod("set_" + this.links_in.get(i), String.class).invoke(response, result.getString(i + 1));
                    } else if (this.links_types.get(i).equals("Double") == true) {
                        response.getClass().getMethod("set_" + this.links_in.get(i), Double.class).invoke(response, result.getDouble(i + 1));
                    } else if (this.links_types.get(i).equals("Date") == true) {
                        response.getClass().getMethod("set_" + this.links_in.get(i), Date.class).invoke(response, result.getDate(i + 1));
                    } else {
                        response.getClass().getMethod("set_" + this.links_in.get(i), Class.forName("java.lang." + this.links_types.get(i))).invoke(response, result.getObject(i + 1));
                    }
                }

            }
            responses.addLast(response);
        }

        connection.close();

        return responses;
    }

    public <T> boolean delete(T deleter) throws InvalidEntityException, SQLException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
        System.out.println("Запуск удалятора 1");

        System.out.println(deleter.getClass());
        System.out.println(this.entity);

        if (deleter.getClass().equals(this.entity) == false) {
            throw new InvalidEntityException();
        }

        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://" + this.host + "/mydb?useUnicode=true&characterEncoding=UTF-8", this.login, this.pass);

        Statement statement = connection.createStatement();

        String SQLQuery = "DELETE";
        String link;
        String where = "";
        Object param;

        for (int i = 0; i < this.links_out.size(); i++) {
            link = this.links_out.get(i);

            if (link.indexOf("|") > -1) {
                String link_left = link.substring(0, link.indexOf("|"));
                String link_right = link.substring(link.indexOf("|") + 1);
                if (deleter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(deleter) != null) {
                    param = deleter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(deleter).getClass().getMethod("get_" + link_right).invoke(deleter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(deleter));
                } else {
                    param = null;
                }

                if (param != null) {
                    link_left = escape(link_left);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    where += "`" + link_left + "` = '" + param + "' AND ";
                }

            } else {

                param = deleter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(deleter);

                if (param != null) {
                    link = escape(link);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    where += "`" + link + "` = '" + param + "' AND ";
                }

            }

        }

        SQLQuery += " FROM " + "`" + this.table + "`";

        if (where.equals("") == false) {
            SQLQuery += " WHERE " + where.substring(0, where.length() - 4);
        }

        System.out.println("Производится запрос: " + SQLQuery);

        boolean res = (statement.executeUpdate(SQLQuery) > 0)?true:false;

        connection.close();

        return res;
    }

    public <T> boolean update(T updater, T newval) throws InvalidEntityException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException, ClassNotFoundException {

        System.out.println("Запуск обновлятор 1");

        System.out.println(updater.getClass());
        System.out.println(this.entity);

        if (updater.getClass().equals(this.entity) == false) {
            throw new InvalidEntityException();
        }

        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://" + this.host + "/mydb?useUnicode=true&characterEncoding=UTF-8", this.login, this.pass);

        Statement statement = connection.createStatement();

        String SQLQuery = "UPDATE ";
        String link;
        String where = "";
        String set = "";
        Object param;

        for (int i = 0; i < this.links_out.size(); i++) {
            link = this.links_out.get(i);


            if (link.indexOf("|") > -1) {
                String link_left = link.substring(0, link.indexOf("|"));
                String link_right = link.substring(link.indexOf("|") + 1);
                if (updater.getClass().getMethod("get_" + this.links_in.get(i)).invoke(updater) != null) {
                    param = updater.getClass().getMethod("get_" + this.links_in.get(i)).invoke(updater).getClass().getMethod("get_" + link_right).invoke(updater.getClass().getMethod("get_" + this.links_in.get(i)).invoke(updater));
                } else {
                    param = null;
                }

                if (param != null) {
                    link_left = escape(link_left);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    where += "`" + link_left + "` = '" + param + "' AND ";
                }

                if (newval.getClass().getMethod("get_" + this.links_in.get(i)).invoke(newval) != null) {
                    param = newval.getClass().getMethod("get_" + this.links_in.get(i)).invoke(newval).getClass().getMethod("get_" + link_right).invoke(newval.getClass().getMethod("get_" + this.links_in.get(i)).invoke(newval));
                } else {
                    param = null;
                }

                if (param != null) {
                    link_left = escape(link_left);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    set += "`" + link_left + "` = '" + param + "', ";
                }

            } else {

                //SQLQuery += table + "." + link + ", ";

                param = updater.getClass().getMethod("get_" + this.links_in.get(i)).invoke(updater);

                if (param != null) {
                    link = escape(link);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    where += "`" + link + "` = '" + param + "' AND ";
                }

                param = newval.getClass().getMethod("get_" + this.links_in.get(i)).invoke(newval);

                if (param != null) {
                    link = escape(link);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    set += "`" + link + "` = '" + param + "', ";
                }

            }
        }

        SQLQuery += " `" + this.table + "`";

        if (set.equals("") == false) {
            SQLQuery += " SET " + set.substring(0, set.length() - 2);
        }

        if (where.equals("") == false) {
            SQLQuery += " WHERE " + where.substring(0, where.length() - 4);
        }

        System.out.println("Производится запрос: " + SQLQuery);

        boolean res = (statement.executeUpdate(SQLQuery) > 0)?true:false;

        connection.close();

        return res;
    }

    public <T> boolean insert(T inserter) throws InvalidEntityException, SQLException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
        System.out.println("Запуск добавлятора 1");

        System.out.println(inserter.getClass());
        System.out.println(this.entity);

        if (inserter.getClass().equals(this.entity) == false) {
            throw new InvalidEntityException();
        }

        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://" + this.host + "/mydb?useUnicode=true&characterEncoding=UTF-8", this.login, this.pass);

        Statement statement = connection.createStatement();

        String SQLQuery = "INSERT";
        String link;
        String val = "";
        String ins = "";
        Object param;

        for (int i = 0; i < this.links_out.size(); i++) {
            link = this.links_out.get(i);

            if (link.indexOf("|") > -1) {
                String link_left = link.substring(0, link.indexOf("|"));
                String link_right = link.substring(link.indexOf("|") + 1);
                if (inserter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(inserter) != null) {
                    param = inserter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(inserter).getClass().getMethod("get_" + link_right).invoke(inserter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(inserter));
                } else {
                    param = null;
                }

                if (param != null) {
                    link_left = escape(link_left);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    ins += "'" + param + "', ";
                    val += "`" + link_left + "`, ";
                }

            } else {

                param = inserter.getClass().getMethod("get_" + this.links_in.get(i)).invoke(inserter);

                if (param != null) {
                    link = escape(link);
                    if (param.getClass().equals(String.class) == true) {param = escape((String)param);}
                    ins += "'" + param + "', ";
                    val += "`" + link + "`, ";
                }

            }

        }

        SQLQuery += " INTO `" + this.table + "`";

        if (val.equals("") == false) {
            SQLQuery += " (" + val.substring(0, val.length() - 2) + ")";
        }

        if (ins.equals("") == false) {
            SQLQuery += " VALUES (" + ins.substring(0, ins.length() - 2) + ")";
        }

        System.out.println("Производится запрос: " + SQLQuery);

        boolean res = (statement.executeUpdate(SQLQuery) > 0)?true:false;

        connection.close();

        return res;
    }
    
    public String escape(String in){
        String out = in;
        out = out.replace("\'", "\\\'");
        return out;
    }
}
